/*-
 * Copyright (c) 2007-2008 SINA Corporation, All Rights Reserved.
 * Author: Zhang shuo <zhangshuo@staff.sina.com.cn>
 * A http protocol implement under Zhu Yan's SASE(csf) framework.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

#include "libprotocol.h"

#include "http.h"
#include "log.h"
#include "confparser.h"
#include "queue.h"
#include "monitor.h"

#define PROTOCOL_NAME "http"

int protocol_init(char *);


/*
 * test vars
 */

static uint64_t test = 123456789123456789LL;
uint32_t test1 = 1234550;
unsigned int test2 = 12345;

int64_t test3 = -1234577790;
int32_t test4 = -223560;
int test5 = -1245;

char test6 = 'v';
float test7 = 1234.345;
double test8 = -134.4534234;

char test9[] = "i am string!";

void init_signal(void);
void handle_signal(int s);
static int http_start(HTTP_REQUEST *req_info);
static void http_data_cleaner(void *ptr);
static ssize_t http_request_responder(CONN_INFO *, void *, int);
static void upload_cleaner(void *ptr);
static ssize_t upload_responder(CONN_INFO *, void *, int);
static void *http_session_start(CONN_INFO *cip);
static int http_session_entry(void *rqstpp, CONN_INFO *cip, void *cli_info, void *data, int len);
static void http_session_end(CONN_INFO *cip, void *ptr);
static void buf_entity(void *rqstpp, HTTP_REQUEST *req_info, char *data, ssize_t len);

/* indicates what header fields to be parsed */
HEADER_FIELD my_header_field[MAX_HEADER_FIELDS] = {
    {"method",              NULL},
    {"uri",                 NULL},
    {"protocol",            NULL},
    {"Authorization",       NULL},
    {"From",                NULL},
    {"If-Modified-Since",   NULL},
    {"Referer",             NULL},
    {"User-Agent",          NULL},
    {"Host",                NULL},
    {"Pragma",              NULL},
    {"Allow",               NULL},
    {"Content-Encoding",    NULL},
    {"Content-Length",      NULL},
    {"Content-Type",        NULL},
    {"Expires",             NULL},
    {"Last-Modified",       NULL},
    {"entity",              NULL}
};

static struct conf_int_config conf_int_array[] = {
	{"max_post_bytes", &MAX_POST_BYTES},
	{0, 0}
};


#ifdef MALLOC_DEBUG
long total_malloc = 0;
long total_free = 0;
#endif

/* 
 * capture signal 13, broken pipe 
 * the default behavior is ignore the signal
 */
void 
init_signal(void)
{
    signal(SIGPIPE,handle_signal);
}


/* do nothing with the signal and reset the signal option */
void 
handle_signal(int s) 
{ 
    CSF_UNUSED_ARG(s);
    init_signal();
}


static void
upload_cleaner(void *ptr)
{
    UPLOAD_PATCH *upload_patch;
    UPLOAD_PATCH *upload_patch_t;
    STQ_HEAD *tailq_head;
    
    tailq_head = (STQ_HEAD *)ptr;
    
    /* scan the link list and free the memory */
    STAILQ_FOREACH_SAFE(upload_patch, tailq_head ,entries, upload_patch_t)
    {
        STAILQ_REMOVE(tailq_head, upload_patch, entry, entries);
        sfree(upload_patch);
        
        #ifdef MALLOC_DEBUG
        total_free++;
        #endif
    }
    
    sfree(tailq_head);

    #ifdef MALLOC_DEBUG
    printf("free=%ld, malloc=%ld\n",total_free, total_malloc);
    #endif
    
    return;
}

static ssize_t 
upload_responder(CONN_INFO *cip, void *ptr, int flag)
{
    CSF_UNUSED_ARG(ptr);
    CSF_UNUSED_ARG(cip);
	CSF_UNUSED_ARG(flag);
    return (RESPONDER_OK);
}

/* a buffer of entity data */
static void 
buf_entity(void *rqstpp, HTTP_REQUEST *req_info, char *data, ssize_t len)
{
    int64_t r;
    UPLOAD_PATCH *upload_patch = NULL;
    STQ_HEAD *header;

    /* upload data is devided into several patches */
    upload_patch = (UPLOAD_PATCH *)smalloc(sizeof(UPLOAD_PATCH));

    if (upload_patch == NULL)
    {
        return;
    }
    
    if (len > UPLOAD_BUF_SIZE)
    {
        WLOG_ERR("[http]'ssize_t len' over limit (UPLOAD_BUF_SIZE)");
        return;
    }

    /* copy the patch data and date length to the new malloced space */
    upload_patch->len = (size_t)len;
    memset(upload_patch->buf, 0, UPLOAD_BUF_SIZE + 1);
    memcpy(upload_patch->buf, data, (size_t)len);
    
    /* add patch to tail queue's tail */
    STAILQ_INSERT_TAIL(&req_info->patch_head, upload_patch, entries);
            
    #ifdef MALLOC_DEBUG
    total_malloc++;
    #endif
    
    /* reach the end of data */
    if (req_info->request_entity_sent_bytes >= req_info->request_entity_len)
    {
        /* temporarily clone the req_info->patch_head struct 
         * and prevent from being free */ 
        header = (STQ_HEAD *)smalloc(sizeof(STQ_HEAD));
        memcpy(header, &req_info->patch_head, sizeof(STQ_HEAD));
        
        r = submit_request(1, rqstpp, header, upload_cleaner, upload_responder, REQUEST_NONE);
        if (r < 0) 
        {
            WLOG_ERR("upload pipeline error. %lX\n", r);
        }
    }
}


/* the entrance of the http server. the function can be attach to a thread */
int
http_start(HTTP_REQUEST *req_info)
{
    /* initialize header_field_ptr */
    memcpy(req_info->header_field_ptr, my_header_field, 
                    MAX_HEADER_FIELDS * sizeof(HEADER_FIELD));
    
    /* parse the request data and store in the header field array */
    return parse_http_request(req_info);
}


static void *
http_session_start(CONN_INFO *cip)
{
    HTTP_REQUEST *my_http_req; 
    
    my_http_req = (HTTP_REQUEST *)smalloc(sizeof(HTTP_REQUEST));
    
    my_http_req->request_fd = cip->fd;
    my_http_req->request_len = 0;  
    my_http_req->request_entity_len = 0;  
    my_http_req->request_entity_sent_bytes = 0;
    my_http_req->is_entity_exist = 0;      
    my_http_req->is_header_processed = 0;  
    my_http_req->is_to_submit = 0;   
    my_http_req->is_submitted = 0; 
    my_http_req->upload_buf_len = 0; 
    my_http_req->state = HTTP_FLOW_CONTINUE;
    my_http_req->request_data = (char *)smalloc(REQUEST_HEADER_BUF_MAX_LENGTH + 1);
    my_http_req->request_data_backup = my_http_req->request_data;
    my_http_req->header_field_ptr = (HEADER_FIELD *)smalloc(MAX_HEADER_FIELDS * sizeof(HEADER_FIELD));
    my_http_req->cip = cip; 
    
    memset(my_http_req->request_data, '\0', REQUEST_HEADER_BUF_MAX_LENGTH + 1);
    memset(my_http_req->upload_buf, 0, UPLOAD_BUF_SIZE + 1);
    
    STAILQ_INIT(&my_http_req->patch_head);
    
    WLOG_ERR("wo kao!!!!");
    
    #ifdef MALLOC_DEBUG
    total_malloc++;
    #endif

   
    return my_http_req;
}


/* get and process TCP socket data */
static int 
http_session_entry(void *rqstpp, CONN_INFO *cip, void *cli_info, void *data, int len)
{  
    ssize_t rest_length_of_buf;     /* header buf remainder length */
    char *req_entity_position = NULL;      /* the start ptr of request entity */
    int temp_len;                   /* temp clone of len */
    HTTP_REQUEST *req_info;         /* the return var from start function */
    int64_t r;
	int i;
    
    temp_len = len;
    req_info = (HTTP_REQUEST *)cli_info;
    rest_length_of_buf = REQUEST_HEADER_BUF_MAX_LENGTH - req_info->request_len;
    
    CSF_UNUSED_ARG(cip);

char wowo[2048];
	const char *haha = "HTTP 200 OK\r\nContent-type: text/html\r\n\r\n""I am the bored words.";
	for (i=0;i<8000;i++){
		snprintf(wowo, 2048, "[%d]%s", i, haha);
	send_back(cip, wowo, strlen(wowo));
	}

	//send_back(cip, "^^^^^^^^^\r\n^^^^^^^\r\n^^^^^^^^", 28);

	
	return PROTOCOL_OK;

	/* gurantee that the total length is not over range */
    if (temp_len > rest_length_of_buf)
    {
        temp_len = rest_length_of_buf;
    }
    
    /* process the request header */
    if (req_info->is_header_processed == 0)
    {
        /* read the requset header */
        if (rest_length_of_buf != 0)
        {
            memcpy(req_info->request_data + req_info->request_len, data, temp_len);
            req_info->request_len += len;
        }

        /* check whether reach the end of request headers '\r\n\r\n' */
        req_entity_position = bsd_strnstr(req_info->request_data, "\r\n\r\n", 
                                                    req_info->request_len);
        if (req_entity_position != NULL)
        {
            req_info->is_header_processed = 1;
            
            if (http_start(req_info) == -1)
            {
                WLOG_ERR("protocol error. socket may lose data.");
                return -1;
            }

            if (req_info->request_entity_len != 0)
            {
                req_info->is_to_submit = 0;     /* do not submit */
                req_info->is_entity_exist = 1;  /* entity data exist. */
            }
            else
            {
                req_info->is_to_submit = 1;    /* no post data. set the submit flag */
            }
        }
    }
    
    /* receive entity data posted */
    if (req_info->is_entity_exist == 1)
    {
        if (req_entity_position != NULL)
        {
            req_info->request_entity_sent_bytes += (req_info->request_len - ((int)(req_entity_position - req_info->request_data_backup) + 4));
            buf_entity(rqstpp, req_info, req_entity_position + 4, req_info->request_entity_sent_bytes);
        }
        else
        {
            req_info->request_entity_sent_bytes += len;
            //buf_entity(rqstpp, req_info, data, len);
            
            /* take the UPLOAD_BUF_SIZE bytes buffer */
            if (req_info->upload_buf_len + len <= UPLOAD_BUF_SIZE)
            {
                memcpy(req_info->upload_buf + req_info->upload_buf_len, data, len);
                req_info->upload_buf_len += len;

                if (req_info->request_entity_sent_bytes >= req_info->request_entity_len)
                {
                    buf_entity(rqstpp, req_info, req_info->upload_buf, req_info->upload_buf_len);
                }
            }
            else
            {
                buf_entity(rqstpp, req_info, req_info->upload_buf, req_info->upload_buf_len);

                req_info->upload_buf_len = 0;
                memcpy(req_info->upload_buf, data, len);
                req_info->upload_buf_len += len;
            }
        }

        /* over limit */
        if (req_info->request_entity_len >= MAX_POST_BYTES)
        {
            WLOG_ERR("[http]post entity over limit");
            return PROTOCOL_DISCONNECT;
        }
        
        /* all data recevied */
        if (req_info->request_entity_sent_bytes >= req_info->request_entity_len)
        {
            req_info->is_to_submit = 1;
            req_info->is_entity_exist = 0;
        }
        
        #ifdef SHOW_UPLOAD_PROGRESS
        if (req_info->request_entity_sent_bytes % 1048576 == 0)
        {
            printf("upload: %0.2f MB\n", (float)(req_info->request_entity_sent_bytes/1048576));
        }
        #endif
    }
    
    /* submit to pipeline */
    if (req_info->is_to_submit == 1)
    {
        req_info->is_to_submit = 0;
        
        r = submit_request(0, rqstpp, req_info, http_data_cleaner, http_request_responder, REQUEST_NONE);
        if (r < 0) 
        {
            WLOG_ERR("pipeline error. %ld\n", r);
            return PROTOCOL_DISCONNECT;
        }
        else
        {
            req_info->is_submitted = 1;
        }
        
        return 0;
    }

    return 0;
}


static void 
http_session_end(CONN_INFO *cip, void *ptr)
{
    HTTP_REQUEST *req_info;
    CSF_UNUSED_ARG(cip);
    
    req_info = (HTTP_REQUEST *)ptr;
    
    /* If the the data is not submitted, we clean it. */
    if (req_info->is_submitted == 0) 
    {
        sfree(req_info->request_data_backup);
        sfree(req_info->header_field_ptr);
        sfree(req_info);
        
        #ifdef MALLOC_DEBUG
        total_free++;
        #endif
    } 

    #ifdef MALLOC_DEBUG
    printf("free=%ld, malloc=%ld\n",total_free, total_malloc);
    #endif
    
    return;
}


static void
http_data_cleaner(void *ptr)
{
    HTTP_REQUEST *req_info;
    req_info = (HTTP_REQUEST *)ptr;
    
    sfree(req_info->request_data_backup);
    sfree(req_info->header_field_ptr);
    sfree(req_info);
        
    #ifdef MALLOC_DEBUG
    total_free++;
    printf("free=%ld, malloc=%ld\n",total_free, total_malloc);
    #endif
    
    return;
}
static ssize_t http_request_responder(CONN_INFO *cip, void *ptr, int flag)
{
    CSF_UNUSED_ARG(ptr);
    CSF_UNUSED_ARG(cip);
	CSF_UNUSED_ARG(flag);
    return (RESPONDER_OK | RESPONDER_DISCONNECT);
}


/* automatically call by csf */
int
protocol_init(char *proto_name)
{
    int r;

    set_protocol_session_start(http_session_start);
	set_protocol_session_entry(http_session_entry);
	set_protocol_session_end(http_session_end);    
    
    /* monitor test */
	
    monitor_var_register("module name", "uint64t", &test, MNT_TYPE_UINT64);
    monitor_var_register("module name", "item name2", &test1, MNT_TYPE_UINT32);
    monitor_var_register("module name", "item name3", &test2, MNT_TYPE_UINT);
    monitor_var_register("module name", "item name4", &test3, MNT_TYPE_INT64);
    monitor_var_register("module name", "item name5", &test4, MNT_TYPE_INT32);
    monitor_var_register("module name", "item name6", &test5, MNT_TYPE_INT);
    monitor_var_register("module name", "item name char", &test6, MNT_TYPE_INT8);
    monitor_var_register("module name", "item name float", &test7, MNT_TYPE_FLOAT);
    monitor_var_register("module name", "item name double", &test8, MNT_TYPE_DOUBLE);
    monitor_var_register("module name", "item name string", &test9, MNT_TYPE_STRING);
    monitor_var_register("module name", "haha", &test9, MNT_TYPE_STRING);


    r = load_conf(NULL, proto_name, conf_int_array, NULL);
	
	if (r != 0)
	{
	    WLOG_ERR("http_init load_conf() failed!");
	}
    
    init_signal();

    return PROTOCOL_OK;
}





